通过向代码生成器添加数组和记录，有时生成的代码不会按预期执行，原因是我们忽略了平台的调用约定。对于同一个程序或库中的函数如何调用另一个函数，每个平台都定义了自己的规则。ABI文档中总结了这些规则，常见信息包括以下内容:

\begin{itemize}
\item
机器寄存器用于参数传递吗?如果是，是哪些?

\item
数组和结构这样的聚合类型如何传递给函数?

\item
如何处理返回值?
\end{itemize}

某些平台上，聚合类型间接传递，所以类型的副本会放在栈上，只有指向副本的指针作为参数传递。其他平台上，在寄存器中传递一个小的聚集(例如128或256位宽)，只有超过该阈值时才使用间接参数传递。有些平台还使用浮点寄存器和向量寄存器来传递参数，而另一些平台则要求浮点值使用整数寄存器传递。

当然，这些都是些底层特性，但它会影响LLVM IR，明明已经在LLVM IR中定义了函数的所有参数类型？!事实证明，这还不够。为了理解这一点，来看一下复数。有些语言内置了复数的数据类型。例如，C99有float \_Complex(以及其他)，旧版本的C没有复数类型，但可以定义struct complex \{float re, im;\}并在此类型上创建算术运算。这两种类型都可以映射到LLVM的\{float, float \} IR类型。

若ABI现在声明内置复数类型的值在两个浮点寄存器中传递，但用户定义的聚合总是间接传递，该函数给出的信息，不足以让LLVM决定如何传递这个特定的参数。但需要向LLVM提供更多的信息，而这些信息等级特定于ABI。

有两种方法可以将这些信息指定给LLVM:参数属性和类型重写，这取决于目标平台和代码生成器。最常用的参数属性如下所示:

\begin{itemize}
\item
inreg指定参数在寄存器中传递

\item
byval指定参数按值传递，该参数必须是指针类型。将指向的数据生成一个隐藏副本，并将该指针传递给调用函数。

\item
zeroext和signext指定传递的整数值是零或符号扩展。

\item
sret指定此形参保存一个指向内存的指针，该指针用于从函数返回聚合类型。
\end{itemize}

虽然所有代码生成器都支持zeroext、signext和sret属性，但只有一些支持inreg和byval。可以使用addAttr()将属性添加到函数的参数中。例如，要在参数Arg上设置inreg属性，可以使用以下方式:

\begin{cpp}
Arg->addAttr(llvm::Attribute::InReg);
\end{cpp}

若需要设置多个属性，可以使用llvm::AttrBuilder类。

提供信息的另一种方法是使用类型重写。使用这种方法，可以隐藏原始类型:

\begin{enumerate}
\item
拆分参数。例如，可以传递两个浮点参数，而不是传递一个复杂参数。

\item
将参数转换为不同的表示形式，例如：通过整数寄存器传递浮点值。
\end{enumerate}

要在不改变值位的情况下强制转换类型，可以使用bitcast指令。位转换指令可以操作简单的数据类型，如整数和浮点值。当通过整数寄存器传递浮点值时，必须将该浮点值强制转换为整数。在LLVM中，32位的浮点数表示为float，32位的整数表示为i32。可以通过以下方式将浮点值转换为整数:

\begin{shell}
%intconv = bitcast float %fp to i32
\end{shell}

此外，位转换指令要求两种类型具有相同的大小。

向参数添加属性或更改类型并不复杂，但如何知道需要实现什么呢?首先，应该大致了解目标平台上使用的调用约定。例如，Linux上的ELF ABI针对每种支持的CPU平台都有文档，可以查找文档并熟悉相关信息。

还有关于LLVM代码生成器需求的文档。信息的来源是clang实现，可以在\url{https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/TargetInfo.cpp}上找到。这个文件包含针对所有受支持平台的ABI操作，也是收集信息的地方。

本节中，了解了如何为函数调用生成与平台的ABI兼容的IR。下一节将为生成类和虚函数的IR创建不同的方法。




















































